Repository navigation
feat: capture native app network through appium-interceptor in every adapter - #445
Merged
Merged
Conversation
A native app has no document and Appium serves no BiDi network events, so a native session's requests reached no Network tab. The appium-interceptor plugin routes the device through a proxy that records them; InterceptorNetworkPoller starts its listener and turns each new entry into a NetworkRequest. It talks to the plugin over raw WebDriver HTTP so its calls never re-enter an adapter's command hooks, and chains drains so overlapping reads cannot list an entry twice.
…intercept Starts the interceptor listener for a native session that asked for the proxy, forwards what it recorded after each top-level command and at session end, and re-arms it on reloadSession because a listener dies with its session. Any other session, or Appium without the plugin, runs as before; the latter logs a warning.
The service README says how to enable it and what a row carries, MOBILE.md lists the remaining adapter gap, and CLAUDE.md's network entry no longer says a native session has no network capture at all.
Contributor
[Medium impact] The PR appears safe to merge; no blocking issue remains from the supplied threads or the latest changes. SummaryAdds native app network capture through appium-interceptor across all four adapters.
All three supplied, unnumbered review threads are addressed: the service drains before writing test traces, header names are lowercased, and Cucumber awaits the step’s drain and trace write. No new actionable issues were found in the changes since the previous review. DiagramsequenceDiagram
participant Runner
participant Adapter
participant Proxy as Appium interceptor
participant Capture
participant Trace
Runner->>Adapter: End test or step
Adapter->>Proxy: Read recorded requests
Proxy-->>Adapter: Listener history
Adapter->>Capture: Add unseen requests
Adapter->>Trace: Write test slice
Adapter-->>Runner: Completion
Runner->>Adapter: Start next test or step
Reviews (5) · Last reviewed commit: "fix(selenium-devtools): await each cucum..." · Reviewed by Greptile |
The intercepted rows, like the two BiDi completion paths in the service's session, were pushed straight onto networkRequests, bypassing the id index the core capturer keeps. A request sent again under the same id was then listed twice instead of replaced. recordNetworkRequests on the core capturer upserts through that index and sends once, and all three sites use it. Also types the capabilities InterceptedNetwork reads instead of casting from unknown, and moves the missing-browser guard on reload into start().
The proxy holds requests made after a test's last command until the next drain, and the end-of-session drain ran after per-test traces were already written, so those requests were missing from them. Header names are lowercased so a mixed-case Content-Type still sets the response type, and the capabilities parameter now accepts the flat requested capabilities the tests and WDIO pass.
… share it Selenium, Nightwatch and the WDIO service all need the same per-session listener, drains and gating, so NativeNetworkCapture moves into core and each adapter supplies only an execute path that renders no rows. The listener also honours intercept inside appium:options, throttles the per-command drain to once every 2 s because every read returns the whole history, and reports a failed read instead of dropping it.
…terceptor A native session's Network tab was always empty under Selenium. When the build asks for appium:intercept, read the plugin's proxy after commands, before each test's trace slice is written and at quit. The slice is chosen before the drain is awaited, so a fast next step cannot take it, and the intercept flag is kept per driver so concurrent builds do not share it.
…interceptor A native session's Network tab was always empty under Nightwatch. Read the plugin's proxy over raw WebDriver HTTP, bound to the session and its configured path prefix, after commands and at each test end, and stop the listener at a session rotation and at the end so no read reaches a session that is gone.
…-interceptor A native app has no document and Appium serves no BiDi network events, so a native session's Network tab was always empty. When the session asks for appium:intercept, read the plugin's proxy after commands, when each test reports its outcome and at quit, mapping each entry to the same request row the JS adapters send. A replaced session's listener is dropped rather than read through the driver's new session.
Each adapter README states the appium:intercept option, the plugin it needs and when the proxy is read, and MOBILE.md and CLAUDE.md now say all four adapters capture native network.
…t starts AfterStep did not wait for onTestEnd, so the next step recorded its trace boundary while this step's native network drain was still in flight, and requests read by that drain landed in the next step's slice.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #444
Why
A native Appium session has no document and Appium serves no BiDi network events, so the Network tab of a native app run is always empty. The page collector and BiDi, which cover every browser session, have nothing to attach to. The traffic is only visible to something sitting between the device and the network, and the appium-interceptor plugin is exactly that: it routes the device through a proxy and exposes what it recorded through
interceptor:execute commands.What
All four adapters read the plugin's proxy when the session asks for it with
appium:intercept: true(top level or insideappium:options, flat or inalwaysMatch/firstMatch) and the session is native.appium-interceptor.ts):InterceptorNetworkPollerstarts the listener (interceptor: startListening), readsinterceptor: getInterceptedDataand turns each entry it has not listed yet into aNetworkRequest. Header names are lowercased, the status line the plugin files as a header is dropped, andsizeis the body's byte length. Drains are chained so two overlapping reads cannot list the same entry, and a read that answers nothing usable is reported rather than dropped.NativeNetworkCaptureowns the lifecycle: gate, one listener per session, drains, stop. Each adapter supplies only an execute path that renders no action rows.NATIVE_NETWORK_DRAIN_INTERVAL_MS). Drains at test end and session end always run.SessionCapturer.recordNetworkRequestsupserts rows through the capturer's index, so a re-sent id replaces its row.reloadSession.executeScript. The intercept flag is kept per driver, so concurrent builds do not share it. The per-test slice is chosen before the drain is awaited, so a fast next step cannot take it. Drains at quit, then stops.default_path_prefix, through core'swebdriverExecute. Drains after commands and at each test end (describe/it and cucumber), and stops the listener at a session rotation and at the end, so no read reaches a session that is gone.interceptor_network.pymirrors the core logic, since the Python adapter ships no Node. Drains after commands, when each test reports its outcome and at quit. A replaced session's listener is dropped rather than read through the driver's new session.examples/MOBILE.mdand the CLAUDE.md network entry. Changesets:@wdio/devtools-service,@wdio/selenium-devtoolsand@wdio/nightwatch-devtoolsminor; Pythonchanges/fragment minor.The plugin records no timing, so a row is stamped when it was first read and shows no duration. Requests the app makes before the listener starts (its launch traffic) are not listed.
Why is this in core
The same lifecycle lands in three JS adapters, so it lives in core and each adapter keeps only its execute path and drain points. It is plain HTTP to Appium and depends on no framework API. Python mirrors it because it cannot import TypeScript.
Scope
The plugin supports Android; HTTPS traffic needs an app that trusts the certificate the plugin installs.
Verification
pnpm test: 214 files, 2785 tests passed. Python suite: 759 tests OK.pnpm buildpasses;pnpm lint0 errors;pnpm typecheckclean; the dist leak grep over service, selenium-devtools and nightwatch-devtools is empty;changes.py checkfinds the fragment.Regression tests fail without their fix: the per-test drain in the service, the Selenium slice race, the Python session replacement, the Nightwatch stop at rotation and the per-driver intercept flag in Selenium.
Not covered by a test: Nightwatch's stop in the plugin's
after(), which has no test harness.Checked on an Android emulator (Android 16,
sdk_gphone64_arm64) with all four adapters, against a test app whose button issues a GET and a POST from Java code (no WebView): in each, the Network tab listsGET → 200andPOST → 201once each asapplication/json, the Actions list carries only the test's own commands, and the requests appear in the trace archive. The app is not public, so that example is not committed. To repeat it with any app that makes HTTP calls, start Appium with the plugin and setappium:intercept: true:Website docs: docs(devtools): Node.js 22.19 floor and native app network capture webdriverio#16176 mirrors the README sections on the
MobileTestingpage.🤖 Generated with Claude Code